/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    file_buffer.cpp  
  Description: 

*/

#include "stdafx.h"


using namespace m::alloc;
using namespace m::error;

namespace m
{
	namespace io
	{
		file_buffer::file_buffer()
			: _FileName(nullptr),
			_Operation(FileOperation::ReadFile),
			_StartOfData(nullptr),
			_Offset(0),
			_BlockStart(0),
			_CurBlock(0),
			_Hr(0)
		{
		}

		file_buffer::~file_buffer()
		{
			clear();
		}

		size_t file_buffer::size() const
		{
			ThrowIfHr();

			size_t ret = 0;
			_file_data* cur = _StartOfData;
			while (cur)
			{
				ret += cur->Length;
				cur = cur->_Next;
			}

			return ret;
		}

		void file_buffer::set_size(const size_t &Size)
		{
			ThrowIfHr();

			size_t oldOffset = offset();
			set_offset(Size);
			size_t newSize = offset();

			if (_CurBlock)
			{
				assert((size_t)_BlockStart <= newSize);
				if ((size_t)_BlockStart <= newSize)
				{
					_CurBlock->split(newSize - _BlockStart, nullptr);
					// Remove all file data to the right of this block.
					_file_data* fd = _CurBlock->_Next;
					while (fd)
					{
						_file_data* next = fd->_Next;
						fd = fd->_Next;
						delete next;
					}
					_CurBlock->_Next = nullptr;
				}
			}

			if (oldOffset > newSize)
			{
				oldOffset = newSize;
			}
			set_offset(oldOffset);
		}

		const ptrdiff_t& file_buffer::offset() const
		{
			ThrowIfHr();
			return _Offset;
		}

		void file_buffer::set_offset(const ptrdiff_t& off)
		{
			ThrowIfHr();

			// Set offset and adjust block and start of data appropriately.
			// We may discover as part of this that the input offset is invalid.
			// This is considered an error condition.

			if (off != _Offset)
			{
				_file_data* cur = _StartOfData;
				ptrdiff_t Start = 0;

				while (cur)
				{
					if (off >= Start && off < Start + (ptrdiff_t)cur->Length)
					{
						// We seeked to our destination.
						_CurBlock = cur;
						_Offset = off;
						_BlockStart = Start;
						return;
					}
					Start += cur->Length;
					cur = cur->_Next;
				}

				throw bad_result_exception(MONORET(FACILITY_FILE, ERROR_INVALID_OPERATION), "Seek failed.");
			}
		}

		size_t file_buffer::read(char * buffer, const size_t & len)
		{
			assert(buffer);
			if (buffer)
			{
				ThrowIfHr();
				size_t remain = len;
				char* cur = buffer;

				while (_CurBlock)
				{
					ptrdiff_t BlockIdx = _Offset - _BlockStart;
					size_t BlockLen = _CurBlock->Length - BlockIdx;
					if (BlockLen > remain)
					{
						BlockLen = remain;
					}
					memcpy(cur, _CurBlock->Buffer + BlockIdx, BlockLen);
					cur += BlockLen;
					remain -= BlockLen;
					_Offset += BlockLen;
					if (remain == 0)
					{
						return len;
					}

					_BlockStart += _CurBlock->Length;
					_CurBlock = _CurBlock->_Next;
				}

				return len - remain;
			}

			// EOF or read failed.
			return 0;
		}

		void file_buffer::write(const char * buffer, const size_t & len)
		{
			assert(buffer);
			if (buffer)
			{
				ThrowIfHr();

				_file_data* cur = _CurBlock;
				if (cur == nullptr)
				{
					_CurBlock = _StartOfData;
					cur = _CurBlock;
				}

				_file_data* newData = new _file_data();
				newData->initialize(buffer, len);
				size_t size = this->size();

				if (cur)
				{
					// Starting at cur, erase data to the right of cur until
					// we've erased len data or we run out of data to erase.
					size_t remaining = size - (size_t)_Offset;
					if (remaining > len)
					{
						remaining = len;
					}

					if (remaining > 0)
					{
						Erase(size, remaining);
					}

					_BlockStart += cur->Length;
					if (cur->_Next)
					{
						cur->_Next->_Prev = newData;
						newData->_Next = cur->_Next;
					}
					cur->_Next = newData;
					newData->_Prev = cur;
				}
				else
				{
					_StartOfData = newData;
					_BlockStart = 0;
				}

				_CurBlock = newData;
				_Offset += len;
			}
		}

		void file_buffer::Erase(size_t startSize, size_t len)
		{
			// First, if the offset isn't the block start we need to split the start block
			// so we can potentially erase the data later.

			_file_data* newData = new _file_data();
			_CurBlock->split(_Offset - _BlockStart, newData);

			if (_CurBlock->_Next)
			{
				newData->_Next = _CurBlock->_Next;
				newData->_Next->_Prev = newData;
			}

			_CurBlock->_Next = newData;
			newData->_Prev = _CurBlock;

			_file_data* cur = _CurBlock->_Next;
			size_t remaining = startSize - _Offset;
			if (remaining > len)
			{
				remaining = len;
			}

			// Now go through the rest of the list erasing data.

			while (cur && remaining > 0)
			{
				if (cur->Length <= remaining)
				{
					// Erase the whole block.
					remaining -= cur->Length;
					if (cur->_Next)
					{
						cur->_Next->_Prev = cur->_Prev;
					}
					if (cur->_Prev)
					{
						cur->_Prev->_Next = cur->_Next;
					}
					_file_data* toDelete = cur;
					cur = cur->_Next;
					delete toDelete;
				}
				else
				{
					// Only erase the n left characters of the block.
					_file_data* replacementData = new _file_data();
					cur->split(remaining, replacementData);
					remaining -= cur->Length;

					if (cur->_Prev)
					{
						cur->_Prev->_Next = replacementData;
						replacementData->_Prev = cur->_Prev;
					}
					if (cur->_Next)
					{
						cur->_Next->_Prev = replacementData;
						replacementData->_Next = cur->_Next;
					}

					delete cur;
					cur = replacementData;
					assert(remaining == 0);
				}
			}
		}

		void file_buffer::clear()
		{
			m::clear(_FileName);
			_file_data* Cur = _StartOfData;
			while (Cur)
			{
				_file_data* toDelete = Cur;
				Cur = Cur->_Next;
				delete toDelete;
			}
			_StartOfData = _CurBlock = nullptr;
			_Offset = _BlockStart = 0;
		}

		void file_buffer::ThrowIfHr() const
		{
			if (!SUCCEEDED(_Hr))
			{
				if (_Operation == FileOperation::ReadFile)
				{
					throw bad_result_exception(_Hr, "Read failed.");
				}
				else if (_Operation == FileOperation::WriteFile)
				{
					throw bad_result_exception(_Hr, "Write failed.");
				}
				assert(false); // Invalid operation.
			}
		}

		void * file_buffer::operator new(size_t sz)
		{
			return ::operator new(sz, pool);
		}

			void file_buffer::operator delete(void * ptr)
		{
			::operator delete(ptr, pool);
		}

	}
}